﻿#region Namespace Imports


// Standard class imports
using System;
using System.Collections.Generic;
using System.Text;

// Supports logging operations
using System.Diagnostics;
using System.Globalization;

// For access to some declarative security types
using System.Security.Permissions;

// Simplifies access to SharePoint-related types
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Security;

// Alias the assembly resources to shorten references.
using res = SPMcDonough.Moss.BlobCacheFarmFlush.Properties.Resources;


#endregion Namespace Imports


namespace SPMcDonough.Moss.BlobCacheFarmFlush
{


    /// <summary>
    /// This class is responsible for handling tasks associated with the sweep job (custom
    /// timer job definition) Feature installation, activation, etc.  For the purposes of 
    /// this Feature, an instance of the custom timer job needs to be bolted to the web
    /// application upon which the Feature is being activated.
    /// <para>This class also takes care of activating and deactivating the admin page
    /// Feature on each of the site collections within the target web application.</para>
    /// </summary>
    public class BlobCacheFarmFlushSweepJobFeatureReceiver : SPFeatureReceiver
    {


        #region Overrides (SPFeatureReceiver)


        /// <summary>
        /// The Feature has been activated.  It is here that the timer job instance 
        /// (of the <see cref="BlobCacheFarmFlushTimerJob"/> type) is created and 
        /// setup to service the web application.
        /// </summary>
        /// <seealso cref="BlobCacheFarmFlushTimerJob"/>
        /// <param name="properties">A reference to the SPFeatureReceiverProperties
        /// object that describes the Feature instance that was activated.</param>
        [SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            // Attempt activation within the context of an exception handling block.
            try
            {
                // Grab a reference to the web app upon which the Feature is being
                // activated, as it will be needed for subsequent operations.
                SPWebApplication housingWebApp = properties.Feature.Parent as SPWebApplication;

                // We'll use the name of the web application to identify the activation
                // target in the log.
                String logMessage = String.Format(CultureInfo.InvariantCulture,
                                                  res.LOG_INFO_FEATURERECEIVERSWEEP_ACTIVATED_MSG_ENTER,
                                                  housingWebApp.Name);

                // Indicate that the FeatureReceiver is getting set to do its thing.
                Globals.LogEvent(logMessage,
                                 res.LOG_INFO_FEATURERECEIVERSWEEP_ACTIVATED_ID_ENTER,
                                 EventLogEntryType.Information);

                // Build the name that would be used for the timer job we're about to create.
                String jobName = BuildTimerJobName(housingWebApp);

                // Create an instance of the timer job.  We'll assign the title we've already
                // calculated and bind it to the current web application.  The final two 
                // parameters should ensure that the job only runs on the server where submitted 
                // (in all likelihood, the central admin host server) and only with one instance 
                // at a time (according to Scot Hillier in
                // http://www.shillier.com/archive/2009/05/01/where-is-my-timer-job.aspx)
                BlobCacheFarmFlushTimerJob newJob = new BlobCacheFarmFlushTimerJob(jobName, housingWebApp, null, SPJobLockType.Job);

                // Format a title for the job using an assembly resource string and the "common"
                // name of the web application
                newJob.Title = String.Format(CultureInfo.InvariantCulture,
                                             res.TIMER_JOB_FORMAT_TITLE,
                                             housingWebApp.Name);

                // We need to establish a recurring schedule of execution for the timer job, as
                // well.  We'll use an interval (specified as another assembly string) to build
                // the appropriate schedule object and assign it to the timer job instance.
                SPSchedule newSchedule = SPSchedule.FromString(res.TIMER_JOB_SCHEDULE_INTERVAL);
                newJob.Schedule = newSchedule;

                // That should be everything needed; update the timer job to persist it with all
                // the desired values into the database.  NOTE: The "true" parameter instructs
                // SharePoint to effectively overwrite an existing instance of the job if one
                // is present on the web app.
                newJob.Update(true);

                // Now that the timer job is active, we need to tie-in at the site collection level.
                // We need to activate the site collection admin page and menu Feature on each
                // site that implements publishing.  To do this expediently, we'll need to create
                // GUIDs for both the BlobCacheFarmFlushAdmin Feature and the SharePoint Publishing
                // Infrastructure Feature.
                Guid featureIdFlushAdmin = new Guid(res.FEATURE_ADMIN_ID);
                Guid featureIdPublishing = new Guid(res.FEATURE_SPPUBLISHING_ID);

                // Now that we have the GUIDs for each of the Features in which we're interested,
                // we need to iterate through each of the site collections in the current web
                // application to see if the BlobCacheFarmFlushAdmin Feature should be activated.
                foreach (SPSite currentSite in housingWebApp.Sites)
                {
                    // Each SPSite created during iteration should be disposed of to avoid
                    // potential leaks.
                    try
                    {
                        // If the publishing infrastructure is active on the current site
                        // collection and the admin Feature isn't, activate the admin Feature.
                        if (currentSite.Features[featureIdPublishing] != null &&
                            currentSite.Features[featureIdFlushAdmin] == null)
                        {
                            currentSite.Features.Add(featureIdFlushAdmin);
                        }
                    }

                    finally
                    {
                        // Release resources held by the site collection reference.
                        if (currentSite != null)
                        {
                            currentSite.Dispose();
                        }
                    }
                }

                // Finish up by establishing the timer job(s) that will update the LAYOUTS pages
                // navigational hierarchy.
                UpdateLayoutsSitemapTimerJob.DeployUpdateTimerJobs(housingWebApp, res.SITEMAP_FILENAME);


                // Indicate successful execution and that the FeatureReceiver is exiting.  Perform
                // the same log message format as earlier in the method ...
                logMessage = String.Format(CultureInfo.InvariantCulture,
                                           res.LOG_INFO_FEATURERECEIVERSWEEP_ACTIVATED_MSG_EXIT,
                                           housingWebApp.Name);
                Globals.LogEvent(logMessage,
                                 res.LOG_INFO_FEATURERECEIVERSWEEP_ACTIVATED_ID_EXIT,
                                 EventLogEntryType.Information);
            }

            catch (Exception ex)
            {
                // An exception was encountered while trying to work with the timer job
                // in some way.  Write out an event log entry describing the problem
                // and what the effects may be.
                //
                // Note: the event log format message string includes a placeholder for
                // the exception detail, so prepare a message with it.
                String errorMessage = String.Format(CultureInfo.InvariantCulture,
                                                    res.LOG_EX_FEATURERECEIVERSWEEP_ACTIVATED_MSG,
                                                    ex.ToString());
                Globals.LogEvent(errorMessage,
                                 res.LOG_EX_FEATURERECEIVERSWEEP_ACTIVATED_ID,
                                 EventLogEntryType.Error);
            }
        }


        /// <summary>
        /// The Feature is being deactivated.  The <see cref="BlobCacheFarmFlushTimerJob"/>
        /// instance servicing the current web application will be removed.
        /// </summary>
        /// <seealso cref="BlobCacheFarmFlushTimerJob"/>
        /// <param name="properties">A reference to the SPFeatureReceiverProperties
        /// object that describes the Feature instance that is being deactivated.</param>
        [SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            // Attempt deactivation within the context of an exception handling block.
            try
            {
                // Grab a reference to the web app upon which the Feature is being
                // deactivated, as it will be needed for subsequent operations.
                SPWebApplication housingWebApp = properties.Feature.Parent as SPWebApplication;

                // We'll use the name of the web application to identify the deactivation
                // target in the log.
                String logMessage = String.Format(CultureInfo.InvariantCulture,
                                                  res.LOG_INFO_FEATURERECEIVERSWEEP_DEACTIVATING_MSG_ENTER,
                                                  housingWebApp.Name);

                // Indicate (in the event log) that the FeatureReceiver is getting set to carry 
                // out its deactivation steps. 
                Globals.LogEvent(logMessage,
                                 res.LOG_INFO_FEATURERECEIVERSWEEP_DEACTIVATING_ID_ENTER,
                                 EventLogEntryType.Information);

                // We need to remove the presence of the admin Feature from any site collection
                // to which it is attached/active in the current web application.  Build the GUID
                // that we'll need for lookups and removal.
                Guid featureIdFlushAdmin = new Guid(res.FEATURE_ADMIN_ID);

                // Iterate through each of the site collections and remove the Feature from
                // them as needed.
                foreach (SPSite currentSite in housingWebApp.Sites)
                {
                    // Each SPSite created during iteration should be disposed of to avoid
                    // potential leaks.
                    try
                    {
                        // If the Feature is present then we'll simply remove it.  NOTE: we'll
                        // do a force if there are errors to prevent (to the extent that we're
                        // able) something from blocking full Feature deactivation and/or
                        // removal at the web app level.
                        if (currentSite.Features[featureIdFlushAdmin] != null)
                        {
                            currentSite.Features.Remove(featureIdFlushAdmin, true);
                        }
                    }

                    finally
                    {
                        // Release resources held by the site collection reference.
                        if (currentSite != null)
                        {
                            currentSite.Dispose();
                        }
                    }
                }

                // The timer job is no longer needed, so we need to delete it from the web application.
                // Build the name that should be used for the timer job.
                String jobName = BuildTimerJobName(housingWebApp);

                // Sweep through the job definitions for the current web application to see if
                // we can find the target job for deletion.  NOTE: we'll process all jobs just in
                // case we end up in a weird situation where more than one instance somehow got
                // bolted to the web app.
                foreach (SPJobDefinition currentJob in housingWebApp.JobDefinitions)
                {
                    if (currentJob.Name == jobName)
                    {
                        // An instance of the job was found.  Delete it.
                        currentJob.Delete();
                    }
                }

                // Add a log entry to indicate that deactivation is concluding; be sure to
                // format the message to include the site collection URL.
                logMessage = String.Format(CultureInfo.InvariantCulture,
                                           res.LOG_INFO_FEATURERECEIVERSWEEP_DEACTIVATING_MSG_EXIT,
                                           housingWebApp.Name);
                Globals.LogEvent(logMessage,
                                 res.LOG_INFO_FEATURERECEIVERSWEEP_DEACTIVATING_ID_EXIT,
                                 EventLogEntryType.Information);
            }

            catch (Exception ex)
            {
                // An exception was encountered while trying to work with the timer job
                // in some way.  Write out an event log entry describing the problem
                // and what the effects may be.
                //
                // Note: the event log format message string includes a placeholder for
                // the exception detail, so prepare a message with it.
                String errorMessage = String.Format(CultureInfo.InvariantCulture,
                                                    res.LOG_EX_FEATURERECEIVERSWEEP_DEACTIVATING_MSG,
                                                    ex.ToString());
                Globals.LogEvent(errorMessage,
                                 res.LOG_EX_FEATURERECEIVERSWEEP_DEACTIVATING_ID,
                                 EventLogEntryType.Error);
            }
        }


        /// <summary>
        /// The Feature has been installed.  No processing will be performed here.
        /// </summary>
        /// <param name="properties">A reference to the SPFeatureReceiverProperties
        /// object that describes the Feature that was installed into the farm's 
        /// solution store.</param>
        [SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
        public override void FeatureInstalled(SPFeatureReceiverProperties properties)
        {
        }


        /// <summary>
        /// The Feature is being uninstalled.  No processing will be performed here.
        /// </summary>
        /// <param name="properties">A reference to the SPFeatureReceiverProperties
        /// object that describes the Feature being uninstalled from the farm.</param>
        [SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
        public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
        {
        }


        #endregion Overrides (SPFeatureReceiver)


        #region Methods (Private, Static)


        /// <summary>
        /// This method is responsible for building the name of the timer job as it
        /// will be registered following instantiation.  This basically involves 
        /// formatting a pre-built resource string with the ID of the 
        /// <paramref name="targetWebApp"/>.
        /// </summary>
        /// <param name="targetWebApp">An SPWebApplication instance that will be the
        /// parent for the timer job instance.</param>
        /// <returns>A String containing a name suitable for assignment to a timer
        /// job (derivative) instance.</returns>
        private static String BuildTimerJobName(SPWebApplication targetWebApp)
        {
            // Simply return the formatted string.
            return String.Format(CultureInfo.InvariantCulture,
                                 res.TIMER_JOB_FORMAT_NAME,
                                 targetWebApp.Id.ToString());
        }


        #endregion Methods (Private, Static)


    }
}
